package com.anjubao.parking.municipal.parkinglot.service;

import com.anjubao.parking.municipal.common.exception.ServiceException;
import com.anjubao.parking.municipal.common.global.BusinessCodeEnum;
import com.anjubao.parking.municipal.common.utils.BeanUtils;
import com.anjubao.parking.municipal.common.utils.MunicipalParkingUtils;
import com.anjubao.parking.municipal.common.utils.U;
import com.anjubao.parking.municipal.parkinglot.constant.RuleTypeEnum;
import com.anjubao.parking.municipal.parkinglot.dto.*;
import com.anjubao.parking.municipal.parkinglot.form.ParkingLotBillRuleForm;
import com.anjubao.parking.municipal.parkinglot.mapper.ParkingLotBillRuleMapper;
import com.anjubao.parking.municipal.parkinglot.mapper.ParkingLotBillRuleRelateMapper;
import com.anjubao.parking.municipal.parkinglot.mapper.ParkingLotBillRuleSubMapper;
import com.anjubao.parking.municipal.parkinglot.model.ParkingLotBillRule;
import com.anjubao.parking.municipal.parkinglot.model.ParkingLotBillRuleRelate;
import com.anjubao.parking.municipal.parkinglot.model.ParkingLotBillRuleSub;
import com.anjubao.parking.municipal.parkinglot.model.ParkingLotSlot;
import com.anjubao.parking.municipal.parkinglot.model.dto.ParkingLotBillRuleModelDto;
import com.anjubao.parking.municipal.parkinglot.util.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 *
 * 停车场计费规则服务
 *
 * @author zhu.chunfeng
 *
 */
@Service
@Slf4j
public class ParkingLotBillRuleService {

    @Resource
    private ParkingLotBillRuleMapper parkingLotBillRuleMapper;
    @Resource
    private ParkingLotBillRuleSubMapper parkingLotBillRuleSubMapper;
    @Resource
    private ParkingLotBillRuleRelateMapper parkingLotBillRuleRelateMapper;
    @Resource
    private ParkingLotSlotService parkingLotSlotService;

    /**
     * 新增计费规则
     * @param form 新增参数
     * @return 数据库实体类
     */
    @Transactional(rollbackFor = Exception.class)
    public ParkingLotBillRule createParkingLotBillRule(ParkingLotBillRuleForm form) {

        Long ruleId = U.id();
        Long subRuleId = U.id();

        /*保存计费规则*/
        ParkingLotBillRule billRule = BeanUtils.transform(ParkingLotBillRule.class, form.getBillRule());
        billRule.setId(ruleId);
        billRule.setSubRuleId(subRuleId);
        billRule.setCode(MunicipalParkingUtils.generateCode(ruleId, BusinessCodeEnum.PAY_BILL_RULE_CODE.getCode()));
        parkingLotBillRuleMapper.insert(billRule);

        ParkingLotBillRuleSub billRuleSub = BeanUtils.transform(ParkingLotBillRuleSub.class, form.getBillRuleSub());
        billRuleSub.setId(subRuleId);
        billRuleSub.setCode(MunicipalParkingUtils.generateCode(subRuleId, BusinessCodeEnum.PAY_BILL_RULE_CODE.getCode()));
        parkingLotBillRuleSubMapper.insert(billRuleSub);

        return billRule;
    }

    /**
     * 更新计费规则
     * @param parkingLotBillRuleCode 计费规则code
     * @param form 编辑参数
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateParkingLotBillRule(String parkingLotBillRuleCode, ParkingLotBillRuleForm form) {

        long ruleId = MunicipalParkingUtils.getIdByCode(parkingLotBillRuleCode);
        ParkingLotBillRule parkingLotBillRule = parkingLotBillRuleMapper.selectById(ruleId);
        if (null == parkingLotBillRule) {
            throw new ServiceException("收费记录不存在");
        }

        /*保存计费规则*/
        ParkingLotBillRule billRule = BeanUtils.transform(ParkingLotBillRule.class, form.getBillRule());
        billRule.setId(ruleId);
        parkingLotBillRuleMapper.updateById(billRule);

        ParkingLotBillRuleSub billRuleSub = BeanUtils.transform(ParkingLotBillRuleSub.class, form.getBillRuleSub());
        billRuleSub.setId(parkingLotBillRule.getSubRuleId());
        int effectNum = parkingLotBillRuleSubMapper.updateById(billRuleSub);
        if (effectNum != 1) {
            throw new ServiceException("数据有误");
        }
    }

    /**
     * 获取计费规则
     * @param parkingLotBillRuleCode 计费规则code
     * @return 数据库实体类
     */
    public ParkingLotBillRuleDto getParkingLotBillRule(String parkingLotBillRuleCode) {
        ParkingLotBillRule parkingLotBillRule = parkingLotBillRuleMapper.selectById(MunicipalParkingUtils.getIdByCode(parkingLotBillRuleCode));
        if (parkingLotBillRule == null) {
            throw new ServiceException("记录不存在");
        }
        ParkingLotBillRuleSub parkingLotBillRuleSub = parkingLotBillRuleSubMapper.selectById(parkingLotBillRule.getSubRuleId());
        if (parkingLotBillRuleSub == null) {
            throw new ServiceException("记录不存在");
        }
        ParkingLotBillRuleDto parkingLotBillRuleDto = new ParkingLotBillRuleDto();
        parkingLotBillRuleDto.setBillRule(BeanUtils.transform(BillRuleDto.class, parkingLotBillRule));
        parkingLotBillRuleDto.setBillRuleSub(BeanUtils.transform(BillRuleSubDto.class, parkingLotBillRuleSub));
        return parkingLotBillRuleDto;
    }

    /**
     * 通过计费规则编号删除实体类
     * @param parkingLotBillRuleCode 计费规则编号
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteParkingLotBillRule(String parkingLotBillRuleCode) {

        ParkingLotBillRule parkingLotBillRule = parkingLotBillRuleMapper.selectById(MunicipalParkingUtils.getIdByCode(parkingLotBillRuleCode));
        if (parkingLotBillRule == null) {
            throw new ServiceException("记录不存在");
        }
        parkingLotBillRuleMapper.deleteById(parkingLotBillRule.getId());
        parkingLotBillRuleSubMapper.deleteById(parkingLotBillRule.getSubRuleId());

    }

    /**
     * 查询计费规则列表
     * @param name 查询条件
     * @param pageNum 页码
     * @param pageSize 页大小
     * @return page
     */
    public IPage<BillRuleDto> getParkingLotBillRulePages(String name, Integer pageNum, Integer pageSize) {
        return parkingLotBillRuleMapper.selectPage(
                new Page<>(pageNum, pageSize),
                new QueryWrapper<>(new ParkingLotBillRule()).lambda().likeRight(StringUtils.isNotBlank(name), ParkingLotBillRule::getName, name)
        ).convert(t -> BeanUtils.transform(BillRuleDto.class, t));
    }


    /**
     * 获取停车总费用
     * @param parkingLotCode 停车场code
     * @param enterTime 进场时间
     * @param leaveTime 离场时间
     * @return 总费用 单位分
     */
    public ParkingTotalFeeDto getParkingTotalFee(String parkingLotCode, LocalDateTime enterTime, LocalDateTime leaveTime) {

        List<ParkingLotBillRuleRelate> parkingLotBillRuleRelates = parkingLotBillRuleRelateMapper.selectList(new QueryWrapper<>(new ParkingLotBillRuleRelate().setParkingLotId(MunicipalParkingUtils.getIdByCode(parkingLotCode))));
        if (CollectionUtils.isEmpty(parkingLotBillRuleRelates)) {
            return new ParkingTotalFeeDto().setTotalFee(1);
//            throw new ServiceException("无计费规则");
        }
        ParkingLotBillRuleRelate parkingLotBillRuleRelate = parkingLotBillRuleRelates.get(0);
        ParkingLotBillRule billRule = parkingLotBillRuleMapper.selectById(parkingLotBillRuleRelate.getParkingLotBillRuleId());
        ParkingLotBillRuleSub billRuleSub = parkingLotBillRuleSubMapper.selectById(billRule.getSubRuleId());
        Integer parkingTotalFee = getParkingTotalFee(enterTime, leaveTime, billRule, billRuleSub);

        return new ParkingTotalFeeDto().setTotalFee(parkingTotalFee);

    }

    private int getParkingTotalFee(LocalDateTime enterTime, LocalDateTime leaveTime,ParkingLotBillRule billRule,ParkingLotBillRuleSub billRuleSub){
        log.info("停车场计费 enterTime:{}, leaveTime:{}",DateUtil.LocalDateTime2String(enterTime),DateUtil.LocalDateTime2String(leaveTime));
        if (enterTime.isAfter(leaveTime)) {
            throw new ServiceException("日期有误");
        }
        //停车时长(秒)
//        int parkingSecond = (int)(leaveTime.toEpochSecond(ZoneOffset.of("+8")) - enterTime.toEpochSecond(ZoneOffset.of("+8")));
        int parkingSecond = (int) Duration.between(enterTime, leaveTime).abs().getSeconds();
        //停车时长(天)
        int parkingDay = (parkingSecond - 1) / (24 * 60 * 60) + 1;

        int totalFee;
        //统一收费
        if (RuleTypeEnum.UNIFY.getCode().equals(billRuleSub.getRuleType())) {
            totalFee = ((parkingSecond - 1) / billRuleSub.getDayTimeSection() + 1) * billRuleSub.getDayFeeSection();
        }
        //分段收费
        else {

            /*计算夜间停车费用*/
            int nightFee = 0;
            //获取夜间停车时长
            int nightTimeSecond = this.getNightTimeSecond(enterTime, leaveTime, billRuleSub.getNightTimeStart(), billRuleSub.getNightTimeEnd());
            if (nightTimeSecond > 0) {
                nightFee = ((nightTimeSecond - 1) / billRuleSub.getNightTimeSection() + 1) * billRuleSub.getNightFeeSection();
            }

            /*计算白天停车费用*/
            int dayFee = 0;
            int dayTimeSecond = parkingSecond - nightTimeSecond;
            if (dayTimeSecond > 0) {
                dayFee = ((dayTimeSecond - 1) / billRuleSub.getDayTimeSection() + 1) * billRuleSub.getDayFeeSection();
            }
//
//            //夜间封顶处理
//            int maxNightFee = parkingDay * billRuleSub.getNightFeeMax();
//            nightFee = nightFee > maxNightFee ? maxNightFee : nightFee;

            totalFee = dayFee + nightFee;
        }

        //封顶处理
        int maxFee = parkingDay * billRule.getDayFeeMax();
        totalFee = totalFee > maxFee ? maxFee : totalFee;
        log.info("停车场计费 enterTime:{}, leaveTime:{}, second:{}, minute:{}, day:{},  totalFee:{}",DateUtil.LocalDateTime2String(enterTime),DateUtil.LocalDateTime2String(leaveTime),parkingSecond,parkingSecond/60,parkingDay,totalFee);
        return totalFee;
    }

    private int getNightTimeSecond(LocalDateTime enterTime, LocalDateTime leaveTime, LocalTime nightTimeStart,LocalTime nightTimeEnd) {

        //便于计算,所有时间按夜间开始时间做偏移
        int i = nightTimeStart.toSecondOfDay();
        enterTime = enterTime.minusSeconds(i);
        leaveTime = leaveTime.minusSeconds(i);
//        nightTimeStart = nightTimeStart.minusSeconds(i);
        nightTimeEnd = nightTimeEnd.minusSeconds(i);

        int nightEndPoint = nightTimeEnd.toSecondOfDay(); //夜间停车结束时间点单位秒 0-?秒

        LocalDateTime dateStart = enterTime.withSecond(0).withMinute(0).withHour(0); //时间轴原点
//        int parkingStartPoint = (int) (enterTime.toEpochSecond(ZoneOffset.of("+8")) - dataStart.toEpochSecond(ZoneOffset.of("+8"))); //开始停车时间
        int parkingStartPoint = (int) Duration.between(dateStart,enterTime).abs().getSeconds(); //开始停车时间,相对于时间轴原点
//        int parkingEndPoint = (int) (leaveTime.toEpochSecond(ZoneOffset.of("+8")) - dataStart.toEpochSecond(ZoneOffset.of("+8"))); //结束停车时间
        int parkingEndPoint = (int) Duration.between(dateStart, leaveTime).abs().getSeconds(); //结束停车时间,相对于时间轴原点

        int totalDay = (parkingEndPoint - 1) / (60 * 60 * 24) + 1; //停车天数
        int nightTimeSecond = totalDay * nightEndPoint; //夜间停车 = 停车天数 * 夜间时长

        //时间调整
        if(parkingStartPoint < nightEndPoint){ //夜间开始停车
            nightTimeSecond -= parkingStartPoint;
        }else {                                 //非夜间开始停车
            nightTimeSecond -= nightEndPoint;
        }
        long reduce = parkingEndPoint % (60 * 60 * 24);
        if (reduce < nightTimeSecond) {   //如果是夜间结束停车
            nightTimeSecond -= (nightEndPoint - reduce);
        }
        return nightTimeSecond;
    }

    public List<ParkingTotalFeeDto> getParkingTotalFeeBatch(List<ParkingTotalFeeDto> totalFeeList) {

        /*数据校验*/
        if (CollectionUtils.isEmpty(totalFeeList)) {
            return new ArrayList<>();
        }
        totalFeeList.forEach(ParkingTotalFeeDto::valid);

        //获取所有停车场id
        Set<Long> parkingLotIds = totalFeeList.stream()
                .map(t->MunicipalParkingUtils.getIdByCode(t.getParkingLotCode()))
                .collect(Collectors.toSet());

        Map<Long, List<ParkingLotBillRuleModelDto>> billRuleByParkingLotIds = this.getBillRuleByParkingLotIds(parkingLotIds);


        return totalFeeList.stream()
                //计算费用
                .peek(t -> {
                    Long parkingLotId = MunicipalParkingUtils.getIdByCode(t.getParkingLotCode());
                    ParkingLotBillRuleModelDto modelDto = billRuleByParkingLotIds.get(parkingLotId).get(0);
                    t.setTotalFee(this.getParkingTotalFee(
                                    DateUtil.string2LocalDateTime(t.getEnterTime()),
                                    DateUtil.string2LocalDateTime(t.getLeaveTime()),
                                    modelDto.getBillRule(),modelDto.getBillRuleSub()));
                        }
                )
                .collect(Collectors.toList());

    }

    /**
     * 根据停车位获取计费规则
     * @param parkinglotSlotName 停车位6位id
     * @return 计费规则详情
     */
    public ParkingLotBillRuleDto getBillRuleByParkingLotSlotName(String parkinglotSlotName) {

        ParkingLotSlot parkingLotSlot = parkingLotSlotService.getParkingLotByName(parkinglotSlotName);

        List<ParkingLotBillRuleRelate> parkingLotBillRuleRelates = parkingLotBillRuleRelateMapper.selectList(new QueryWrapper<>(new ParkingLotBillRuleRelate().setParkingLotId(parkingLotSlot.getParkingLotId())));
        if (CollectionUtils.isEmpty(parkingLotBillRuleRelates)) {
            throw new ServiceException("无停车位与计费规则关联关系");
        }
        ParkingLotBillRuleRelate byParkingLotId = parkingLotBillRuleRelates.get(0);

        return this.getParkingLotBillRule(MunicipalParkingUtils.generateCode(byParkingLotId.getParkingLotBillRuleId(), BusinessCodeEnum.PAY_BILL_RULE_CODE.getCode()));
    }

    /**
     * 根据停车场id找计费规则
     * @param parkingLotIds 根据停车场ids
     * @return 停车场id -> 计费规则列表
     */
    public Map<Long,List<ParkingLotBillRuleModelDto>> getBillRuleByParkingLotIds(Set<Long> parkingLotIds) {

        if (CollectionUtils.isEmpty(parkingLotIds)) {
            return new HashMap<>();
        }

        List<ParkingLotBillRuleRelate> parkingLotBillRuleRelates = parkingLotBillRuleRelateMapper.selectList(
                new QueryWrapper<>(new ParkingLotBillRuleRelate()).lambda()
                        .in(ParkingLotBillRuleRelate::getParkingLotId, parkingLotIds)
        );
        //停车场id -> 计费关联关系表s
        Map<Long, List<ParkingLotBillRuleRelate>> parkinglotMapBillRuleRelates = parkingLotBillRuleRelates.stream().collect(Collectors.groupingBy(ParkingLotBillRuleRelate::getParkingLotId));

        Set<Long> ruleIds = parkingLotBillRuleRelates.stream().map(ParkingLotBillRuleRelate::getParkingLotBillRuleId).collect(Collectors.toSet());
        List<ParkingLotBillRule> parkingLotBillRules = parkingLotBillRuleMapper.selectBatchIds(ruleIds);
        Map<Long, ParkingLotBillRule> idMapBillRule = parkingLotBillRules.stream().collect(Collectors.toMap(ParkingLotBillRule::getId, t -> t, (t, v) -> t));

        Set<Long> ruleSubIds = parkingLotBillRules.stream().map(ParkingLotBillRule::getSubRuleId).collect(Collectors.toSet());
        List<ParkingLotBillRuleSub> parkingLotBillRuleSubs = parkingLotBillRuleSubMapper.selectBatchIds(ruleSubIds);
        Map<Long, ParkingLotBillRuleSub> idMapBillRuleSub = parkingLotBillRuleSubs.stream().collect(Collectors.toMap(ParkingLotBillRuleSub::getId, Function.identity(), (t, v) -> t));

        Map<Long, List<ParkingLotBillRuleModelDto>> map = new HashMap<>();
        parkinglotMapBillRuleRelates.forEach((key, value) -> {
            List<ParkingLotBillRuleModelDto> collect = value.stream()
                    .map(v -> {
                        ParkingLotBillRuleModelDto modelDto = new ParkingLotBillRuleModelDto();
                        ParkingLotBillRule billRule = idMapBillRule.get(v.getParkingLotBillRuleId());
                        ParkingLotBillRuleSub billRuleSub = idMapBillRuleSub.get(billRule.getSubRuleId());
                        modelDto.setBillRule(billRule);
                        modelDto.setBillRuleSub(billRuleSub);
                        return modelDto;
                    })
                    .collect(Collectors.toList());
            map.put(key, collect);
        });
        return map;

    }
}




